package br.com.pd.invocation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import br.com.pd.messaging.MessageFactory;
import br.com.pd.messaging.MessageIF;
import br.com.pd.messaging.ManagerRequestHandle;
import br.com.util.PropertiesLoader;


public class Invoker extends Thread implements InvokerIF{
	
	private MarshallIF marshall;
	private Map<String, SupplyListener> listeners;
	private List<InvokerInfo> messages;
	private List<MessageIF> supplyMessages;
	private List<MessageIF> ackMessages;
	private int checkerPeriod; 
	private ManagerRequestHandle requestHandle;
	
	public Invoker(ManagerRequestHandle s){
		this.requestHandle = s;
		this.marshall = new Marshaller();
		this.messages = new ArrayList<InvokerInfo>();
		this.supplyMessages = new ArrayList<MessageIF>();
		this.ackMessages = new ArrayList<MessageIF>();
		this.listeners = new HashMap<String, SupplyListener>();
		this.checkerPeriod = Integer.parseInt(PropertiesLoader.getInstance().getPropertie(PropertiesLoader.MESSAGE_CHECKER_PERIOD));
		this.start();
	}
	
	@Override
	// decodificar com o marshall 
	// invokar o metodo do objeto remoto (usar reflection )
	// enviar a mensagem de volta
	public void execute(String m) {
		MessageIF message = marshall.unMarshall(m);
		if (message.getOperation().equals(MessageIF.SUBSCRIBE)) {
			message.addResponse("ACK-OK");
			listeners.put(message.getFrom(),new SupplyListener(message.getFrom(), message.getPortSender()));
			this.ackMessages.add(message);
		}else if(message.getOperation().equals(MessageIF.SUPPLY)){
			for (InvokerInfo i : this.messages) {
				if (i.getCode().equals(message.getId())) {
					MessageIF newMessage = this.marshall.unMarshall(i.getMessage());
					newMessage.addResponse(message.getResponse());
					i.addResponse();
					i.setMessage(this.marshall.marshall(newMessage));
				}
			}
		}else if(message.getOperation().equals(MessageIF.COTATION)){
			InvokerInfo invInf = new InvokerInfo(message.getFrom(), this.listeners.size(),m);
			this.messages.add(invInf);
			Iterator<String> it = this.listeners.keySet().iterator();
			while (it.hasNext()) {
				SupplyListener s = this.listeners.get(it.next());
				MessageIF tmp =  MessageFactory.createSupplyMessage(message.getParams());
				tmp.setId(invInf.getCode());
				tmp.setTo(s.getIp());
				tmp.setPortReceiver(s.getPort());
				tmp.setPortSender(message.getPortReceiver());
				this.supplyMessages.add(tmp);
			}
		}
	}
	
	@Override
	public void run(){
		while(true){
			try {
				this.verifyReturnMessages();
				this.verifySupplyMessages();
				this.verifyAckMessages();
				this.verifyListernes();
				this.sleep(checkerPeriod);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	private void verifyListernes(){
		Iterator<String> it = this.listeners.keySet().iterator();
		List<String> keys = new ArrayList<String>();
		while (it.hasNext()) {
			String key  = (String) it.next();
			if(this.listeners.get(key).isExprire()){
				keys.add(key);
			}
		}
		for (String key : keys) {
			this.listeners.remove(key);
		}
		
	}
	
	private void verifyReturnMessages(){
		List<InvokerInfo> tmp = new ArrayList<InvokerInfo>();
		for (InvokerInfo i : this.messages) {
			if (i.isComplete()) {
				tmp.add(i);
			}
		}
		for (InvokerInfo i : tmp) {
			this.messages.remove(i);
			MessageIF m = this.marshall.unMarshall(i.getMessage());
			this.requestHandle.send(i.getMessage(), m.getFrom(), m.getPortSender());
		}
	}
	
	private void verifySupplyMessages(){
		List<MessageIF> tmp = new ArrayList<MessageIF>();
		for (MessageIF i : this.supplyMessages) {
			tmp.add(i);
		}
		for (MessageIF i : tmp) {
			this.supplyMessages.remove(i);
			String m = this.marshall.marshall(i);
			this.requestHandle.send(m , i.getFrom(), i.getPortReceiver());
		}
	}
	
	
	private void verifyAckMessages(){
		List<MessageIF> tmp = new ArrayList<MessageIF>();
		for (MessageIF i : this.ackMessages) {
			tmp.add(i);
		}
		for (MessageIF i : tmp) {
			this.ackMessages.remove(i);
			String m = this.marshall.marshall(i);
			this.requestHandle.send(m , i.getFrom(), i.getPortSender());
		}
	}
	

}
